package game.ai;

import engine.j2d.Graphics;
import game.components.Collideble;
import game.components.CollidebleStatic;
import game.components.IAlive;
import game.components.IGameElement;
import game.components.IRenderable;
import game.components.IUpdateable;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.TreeMap;

import net.phys2d.math.ROVector2f;
import net.phys2d.math.Vector2f;
import net.phys2d.raw.Body;
import net.phys2d.raw.BodyList;
import net.phys2d.raw.StaticBody;
import net.phys2d.raw.strategies.QuadSpaceStrategy;



/**
 * helps to work with tiles, backgrounds layers and Physics
 */
public class World {
	
	protected HashSet<IUpdateable> setIUpdateble = new HashSet<IUpdateable>();
	protected TreeMap<Integer, List<IGameElement>> mapIRenderable = new TreeMap<Integer, List<IGameElement>>();
	
	private static World world = new World();
	private static net.phys2d.raw.World world_phys = new net.phys2d.raw.World(
			new Vector2f(0, 9.8f), 4, new QuadSpaceStrategy(20, 5));
	
	public static World getInstance() {
		return world;
	}
	
	private World() {
		// ignore;
	}
	
	public void addElement(Integer loyerCount, IGameElement spr) {
		if (spr instanceof IUpdateable) {
			setIUpdateble.add((IUpdateable)spr);	
		} 
		if (spr instanceof IRenderable) {
			addToSpesialMap(loyerCount, spr, mapIRenderable);
		} 
		if (spr instanceof Collideble || spr instanceof CollidebleStatic) {
			Body body = (Body)spr;
			world_phys.add(body);
		}
	}
	
	public void addBody(Body body) {
		world_phys.add(body);
	}
	
	private void addToSpesialMap(Integer loyerCount, IGameElement spr, TreeMap<Integer, List<IGameElement>> map){
		if (map.containsKey(loyerCount)) {
			List<IGameElement> list = map.get(loyerCount);
			list.add(spr);
		} else {
			List<IGameElement> list = new ArrayList<IGameElement>(10);
			list.add(spr);
			map.put(loyerCount, list);
		}	
	}
	
	public void removeElement(IGameElement spr) {
		setIUpdateble.remove(spr);
		for (List<IGameElement> loyer : mapIRenderable.values()) {
			if (loyer.contains(spr)){
				loyer.remove(spr);
			}
		}
		if (spr instanceof Body) {
			Body body = (Body) spr;
			world_phys.remove(body);
		}
	}
	
	public void render(Graphics g) {
		for (Integer loyer : mapIRenderable.descendingKeySet()) {
			for (IGameElement element : mapIRenderable.get(loyer)) {
				IRenderable randable = (IRenderable) element;
				randable.render(g);
			}
		}
	}
	public void update(float delta_t) {
		ArrayList<IUpdateable> tmpIUpdateble = new ArrayList<IUpdateable>(setIUpdateble);
		for (IGameElement element : tmpIUpdateble) {
			IUpdateable undateable = (IUpdateable) element;
			undateable.update(delta_t);
		}
	}
	
	public void explosion(ROVector2f positionOfExp, float radiusOfExp, float forceOfExp) {
		BodyList list = world_phys.getBodies();
		for (int i = 0; i < list.size(); i++) {
			Body body = list.get(i);
			if (!(body instanceof StaticBody)) {
				float distance = positionOfExp.distance(body.getPosition());
				if (distance < radiusOfExp) {
					Vector2f directionOfExplosionForce = new Vector2f(body.getPosition());
					directionOfExplosionForce.sub(positionOfExp);
					directionOfExplosionForce.normalise();
					directionOfExplosionForce.scale(forceOfExp * (1 - distance/radiusOfExp));
					body.addForce(directionOfExplosionForce);
					if (body instanceof IAlive) {
						IAlive alive = (IAlive) body;
						alive.damage((1 - distance / radiusOfExp) * 50);
					}
				}
			}
		}
	}
	
	public void runNextStep() {
		world_phys.step();
	}
	
	public void initialise() {
		clear();
		System.gc();
	}
	
	public void clear() {
		setIUpdateble = new HashSet<IUpdateable>();
		mapIRenderable = new TreeMap<Integer, List<IGameElement>>();
		world_phys.clear();
	}

}
